package com.cch.cooperation.api.support.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.http.MethodType;
import com.cch.cooperation.api.common.properties.SmsProperties;
import com.cch.cooperation.api.support.dto.SendCodeReqDTO;
import com.cch.cooperation.api.support.dto.SmsCodeDTO;
import com.cch.cooperation.api.support.util.PasswordUtil;
import com.cch.cooperation.common.exception.BizException;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 短信服务
 *
 * @author caich
 **/
@Slf4j
@Service
public class SmsServiceImpl {

    @Resource
    private IAcsClient acsClient;
    @Resource
    private SmsProperties smsProperties;
    @Resource
    private RedissonClient redissonClient;

    private final static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 5, TimeUnit.MINUTES, new ArrayBlockingQueue<>(500), new ThreadPoolExecutor.AbortPolicy());

    private static final String SMS_CODE_KEY = "SMS:CODE:";

    public void sendSmsCodeWithBackup(String phoneNumber, Map<String, String> templateParam) {
        String bizId = sendSms(phoneNumber, smsProperties.getSignName(), smsProperties.getCodeTemplateCode(), templateParam);
        // 监控短信发送状态
        executor.submit(() -> {
            int waitCount = 0;
            while(waitCount++ < 9) {
                try {
                    ThreadUtil.sleep(20_000);
                    JSONObject statusResp = getSmsStatus(phoneNumber, bizId);
                    JSONArray sends = statusResp.getJSONObject("SmsSendDetailDTOs").getJSONArray("SmsSendDetailDTO");
                    if (CollUtil.isEmpty(sends)) {
                        continue;
                    }
                    JSONObject sendDetail = sends.getJSONObject(0);
                    if ("1".equals(sendDetail.getString("SendStatus"))) {
                        continue;
                    }
                    if ("3".equals(sendDetail.getString("SendStatus"))) {
                        return;
                    }
                    log.warn("短信发送失败,即将使用备用签名重发, 手机号：{}，bizId：{}，返回信息：{}", phoneNumber, bizId, statusResp);
                    sendSms(phoneNumber, smsProperties.getBackupSignName(), smsProperties.getBackupCodeTemplateCode(), templateParam);
                    return;
                } catch (Exception e) {
                    log.error("查询短信状态异常, 手机号：{}，bizId：{}，异常信息：{}", phoneNumber, bizId, e.getMessage(), e);
                }
            }
        });
    }

    public String sendSms(String phoneNumber, String signName, String templateCode, Map<String, String> templateParam) {
        try {
            CommonRequest request = new CommonRequest();
            request.setMethod(MethodType.POST);
            request.setConnectTimeout(5000);
            request.setReadTimeout(5000);
            request.setAction("SendSms");
            request.setDomain("dysmsapi.aliyuncs.com");
            request.setVersion("2017-05-25");
            request.putQueryParameter("PhoneNumbers", phoneNumber);
            request.putQueryParameter("SignName", signName);
            request.putQueryParameter("TemplateCode", templateCode);
            request.putQueryParameter("TemplateParam", JSON.toJSONString(templateParam));
            CommonResponse response = acsClient.getCommonResponse(request);
            JSONObject result = JSON.parseObject(response.getData());
            if ("isv.BUSINESS_LIMIT_CONTROL".equals(result.get("Code"))) {
                log.error("当前时间段内发送消息已达上限,返回提示:{}", result);
                throw new BizException("当前时间段内发送消息已达上限");
            }
            if (!"OK".equals(result.get("Code"))) {
                log.error("发送短信失败,返回提示:{}", result);
                throw new BizException("发送短信失败");
            }
            return result.getString("BizId");
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            log.error("发送短信失败", e);
            throw new BizException("发送短信失败", e);
        }
    }

    public JSONObject getSmsStatus(String phoneNumber, String bizId) {
        try {
            CommonRequest request = new CommonRequest();
            request.setMethod(MethodType.POST);
            request.setConnectTimeout(5000);
            request.setReadTimeout(5000);
            request.setAction("QuerySendDetails");
            request.setDomain("dysmsapi.aliyuncs.com");
            request.setVersion("2017-05-25");
            request.putQueryParameter("PhoneNumber", phoneNumber);
            request.putQueryParameter("BizId", bizId);
            request.putQueryParameter("SendDate", DateUtil.format(new Date(), "yyyyMMdd"));
            request.putQueryParameter("CurrentPage", "1");
            request.putQueryParameter("PageSize", "1");
            CommonResponse response = acsClient.getCommonResponse(request);
            return JSON.parseObject(response.getData());
        } catch (Exception e) {
            log.error("查询短信状态异常, 手机号：{}，bizId：{}，异常信息：{}", phoneNumber, bizId, e.getMessage(), e);
            return null;
        }
    }

    public String sendCode(@Valid SendCodeReqDTO req) throws Exception {
        if (StrUtil.isEmpty(req.getPhoneNumber())) {
            throw new BizException("手机号不能为空");
        }
        long now = System.currentTimeMillis();
        // 校验短信是否频繁发送
        String key = SMS_CODE_KEY + req.getPhoneNumber();
        RBucket<SmsCodeDTO> bucket = redissonClient.getBucket(key);
        SmsCodeDTO smsCode = bucket.get();
        if (ObjectUtil.isNotNull(smsCode) && (now - smsCode.getCreateTime()) < TimeUnit.MINUTES.toMillis(1)) {
            throw new BizException("您的短信发送过于频繁");
        }
        // 创建验证码
        String code = PasswordUtil.randomNum(6);
        smsCode = new SmsCodeDTO();
        smsCode.setCode(code);
        smsCode.setPhone(req.getPhoneNumber());
        smsCode.setCreateTime(now);
        bucket.set(smsCode, 5, TimeUnit.MINUTES);
        // 发送验证码
        Map<String, String> params =  ImmutableMap.of("code", code, "minute", "5");
        sendSmsCodeWithBackup(req.getPhoneNumber(), params);
        // 异步监听短信状态
        return code;
    }

    public SmsCodeDTO getCode(String phone) {
        if (StrUtil.isEmpty(phone)) {
            throw new BizException("手机号不能为空");
        }
        String key = SMS_CODE_KEY + phone;
        RBucket<SmsCodeDTO> bucket = redissonClient.getBucket(key);
        return bucket.get();
    }

    public void useCode(String phone, String code) {
        if (StrUtil.isEmpty(code)) {
            throw new BizException("验证码不能为空");
        }
        if ("15888888888".equals(phone) && code.equals("666888")) {
            return;
        }
        SmsCodeDTO smsCode = getCode(phone);
        if (smsCode == null) {
            throw new BizException("验证码已失效");
        }
        if (!code.equalsIgnoreCase(smsCode.getCode())) {
            throw new BizException("验证码错误");
        }
        String key = SMS_CODE_KEY + phone;
        RBucket<SmsCodeDTO> bucket = redissonClient.getBucket(key);
        bucket.delete();
    }
}
